import datetime
import json
import os
from typing import Optional, List, Any, Dict, Union, Tuple
from xml.etree import ElementTree
import psycopg2
import psycopg2.extras
from shapely.geometry.linestring import LineString
from shapely.geometry.multilinestring import MultiLineString
from shapely.geometry.point import Point
from shapely.wkt import loads
from shapely import MultiPolygon, Polygon

class DBConnector:
    def __init__(self, config_file="", use_env=False, config_obj=None):
        """
        有三种方式设置数据库连接参数：
        第一种是读配置文件，用config_file参数指定文件路径，配置文件格式如下：
        {
        "postgres":
        {
            "database": "postgres",
            "user": "gis_read",
            "password": "isd5Wp3h2sqQ",
            "host": "192.168.1.53",
            "port": "5432"
        }
        }
        第二种是从环境变量中读取，指定use_env参数为True，设置环境变量P2ODB，P2OUSER，P2OPSWD，P2OHOST，P2OPORT的值
        当config_file和use_env参数都有效时忽略环境变量的值而从文件读取配置。
        第三种是传config_obj参数直接赋值{"database":xxx, "user":xxx, "password":xxx, "host":xxx, "port":xxx}
        """
        self.connection_config = {}
        self.conn = None
        self.cursor = None
        self.config_file = config_file
        self.use_env = use_env
        if config_obj:
            self.connection_config = config_obj
        else:
            self.__config()

    def __config(self):
        if self.config_file != "":
            with open(self.config_file, "rt") as f:
                json_data = json.loads(f.read())
                self.connection_config = {
                    "database": json_data["postgres"]["database"],
                    "user": json_data["postgres"]["user"],
                    "password": json_data["postgres"]["password"],
                    "host": json_data["postgres"]["host"],
                    "port": json_data["postgres"]["port"]
                }
                f.close()
        elif self.use_env:
            self.connection_config = {
                "database": os.environ.get('P2ODB'),
                "user": os.environ.get('P2OUSER'),
                "password": os.environ.get('P2OPSWD'),
                "host": os.environ.get('P2OHOST'),
                "port": os.environ.get('P2OPORT')
            }

    def connect(self):
        if (len(self.connection_config) == 0 or
                not (self.connection_config["database"]
                     and self.connection_config["user"]
                     and self.connection_config["password"]
                     and self.connection_config["host"]
                     and self.connection_config["port"])):
            raise Exception("数据库连接配置有误，请检查。")
        self.conn = psycopg2.connect(dbname=self.connection_config["database"], user=self.connection_config["user"],
                                     password=self.connection_config["password"], host=self.connection_config["host"],
                                     port=self.connection_config["port"])
        self.cursor = self.conn.cursor(cursor_factory=psycopg2.extras.RealDictCursor)

    def close(self):
        self.conn.close()


class OSMLine:
    def __init__(self, version=1, time=datetime.datetime.now()):
        self.nd = []
        self.tags = {"comment": "from(multi)line_string"}
        self.user = "python_auto"
        self.uid = "9527"
        self.changeset = "1"
        self.id = AutoIncreasedID.next_id()
        self.version = str(version)
        self.timestamp = time.strftime("%Y-%m-%dT%H:%M:%SZ") # "1970-01-01T00:00:00Z"

    def to_xml_element(self):
        attrs_of_way = {"id": str(self.id), "user": self.user, "uid": self.uid, "version": self.version,
                        "changeset": self.changeset, "timestamp": self.timestamp}
        way = ElementTree.Element("way", attrs_of_way)
        for k,v in self.tags.items():
            way.append(ElementTree.Element("tag", {"k":k, "v":v}))
        for i in self.nd:
            way.append(ElementTree.Element("nd", {"ref": str(i)}))
        return way

class RelationMember:
    def __init__(self, rtype: str, ref: str, role: str):
        self.rtype = rtype
        self.ref = ref
        self.role = role

class OSMRelation:
    """
    OSM中的relation,目前暂只用来描述multipolygon对象：
    将一个out way和多个inner way放一起生成含有孔洞的polygon
    """
    def __init__(self, version=1, time=datetime.datetime.now()):
        self.members : List[RelationMember] = []
        self.tags = {}  # 转成xml后<tag k="", v="" />
        self.user = "python_auto"
        self.uid = "9527"
        self.changeset = "1"
        self.id = AutoIncreasedID.next_id()
        self.version = str(version)
        self.timestamp = time.strftime("%Y-%m-%dT%H:%M:%SZ") # "1970-01-01T00:00:00Z"

    def to_xml_element(self):
        attrs = {"id": str(self.id), "user": self.user, "uid": self.uid, "version": self.version,
                        "changeset": self.changeset, "timestamp": self.timestamp}
        relation = ElementTree.Element("relation", attrs)
        for i in self.members:
            relation.append(ElementTree.Element("member", {"type": i.rtype, "ref": i.ref, "role": i.role}))
        for k,v in self.tags.items():
            relation.append(ElementTree.Element("tag", {"k":k, "v":v}))
        return relation


class AutoIncreasedID:
    """
    用于生成自增id，用作.osm的id，在osm中很多复杂对象的结构都是对基本对象的引用，所以需要个不重复的id。事实上osm的relation/way/node
    允许有各自独立的id（即way和node的id可以一样）但是我们先暂定所有id都是唯一的。
    随着数据的膨胀，未来可能需要使用多线程或者多进程将postgres的数据按照地理空间（或其他方式）进行分隔处理。
    """
    id4osm = 0
    @staticmethod
    def next_id():
        AutoIncreasedID.id4osm += 1
        return AutoIncreasedID.id4osm

# 一个node节点
# 必要属性："id", "lat", "lon", "user", "uid", "version", "changeset", "timestamp"
# 数据库中读取出来的点对象还有其他字段，它们被放到NodeOSM.tags中。
class OSMNode:
    def __init__(self, lat=0.0, lon=0.0, version=1, time=datetime.datetime.now()):
        self.user = "python_auto"
        self.uid = "9527"
        self.changeset = "1"
        self.tags = {}
        self.id = AutoIncreasedID.next_id()
        self.lat = str(lat)
        self.lon = str(lon)
        self.version = str(version)
        self.timestamp = time.strftime("%Y-%m-%dT%H:%M:%SZ")

    def set_tags(self, l):
        self.tags = l

    def to_xml_element(self):
        """
        将对象转为xml的一个节点并返回。
        :return: xml.etree.ElementTree.Element对象。
        """
        attrs = {"id": str(self.id),
                 "lat": format(float(self.lat), ".10f").rstrip('0').rstrip("."),
                 "lon": format(float(self.lon), ".10f").rstrip('0').rstrip("."),
                 "user": "pyosm",
                 "uid": "9527",
                 "version": self.version,
                 "changeset": "1",
                 "timestamp": self.timestamp}
        node =  ElementTree.Element("node", attrs)
        for i,val in self.tags.items():
            attrs = {"k": i, "v": val}
            ele = ElementTree.Element("tag", attrs)
            node.append(ele)
        return node

def find_exist_node(lat, lon):
    for i in point_nodes_in_xml:
        if i.lat == lat and i.lon == lon:
            return i
    return None


point_nodes_in_xml: List[OSMNode] = []
lines_in_xml: List[OSMLine] = []
relations_in_xml: List[OSMRelation] = []

def clean_osm_elements():
    point_nodes_in_xml.clear()
    lines_in_xml.clear()
    relations_in_xml.clear()

def write_xml(file_full_path):
    root = ElementTree.Element("osm", {"version": "0.6", "generator": "hand"})
    for i in point_nodes_in_xml:
        root.append(i.to_xml_element())
    for i in lines_in_xml:
        root.append(i.to_xml_element())
    for i in relations_in_xml:
        root.append(i.to_xml_element())
    tree = ElementTree.ElementTree(root)
    tree.write(file_full_path, encoding="UTF-8", xml_declaration=True)
    print("已写入测试文件")

def copy_common_attrs(attrs:Dict[str,Any], copy_to:Dict[str,str]):
    for k, v in attrs.items():
        # 1. 忽略已经使用的属性
        if k in ["geom", "rver", "updated_at", "geo"]:  # TODO 未来根据需要再精简掉那些导航不需要的字段。
            continue
        if v is None:  # tags忽略空值
            continue
        copy_to[k] = str(v)

def common_points(row: psycopg2.extras.RealDictRow, from_gre_table: Optional[str]) -> OSMNode:
    """
    :param row: RealDictRow [('objl', 159), ('acronym', 'WRECKS'),...]
    :param from_gre_table: 来自哪张表
    :return:
    """
    attrs: Dict[str, Any] = dict(row)
    point_obj = loads(attrs["geo"])
    assert isinstance(point_obj, Point)
    #print(point_obj, type(point_obj), point_obj.x, point_obj.y)
    # 直接转为xml是不行的，因为没有id。
    n = OSMNode(point_obj.y, point_obj.x, attrs["rver"], attrs["updated_at"])
    tag_pairs = {}
    if from_gre_table:
        tag_pairs["from_table"] = from_gre_table
    copy_common_attrs(attrs, tag_pairs)
    n.set_tags(tag_pairs)
    # if 1:
    #     print(type(row), row)
    #     print(len(attrs), attrs)
    #     print(attrs["geom"], point_obj.x, point_obj.y)
    #     print(type(attrs["geom"]),type(point_obj.x),type(point_obj.y))
    #     print(attrs["updated_at"], type(attrs["updated_at"]),attrs["updated_at"].strftime("%Y-%m-%dT%H:%M:%SZ"))
    #     print(n.to_xml_str())
    #     print(len(tag_pairs), tag_pairs)
    #     # xml_node = n.to_xml_element(); ET.indent(xml_node) # since python 3.9
    #     print(ET.tostring(n.to_xml_element(), encoding='unicode'), "\n")
    point_nodes_in_xml.append(n)
    return n

def common_line(row, from_gre_table)->Tuple[List[OSMNode], List[OSMLine], List[OSMRelation]]:
    """
    处理LineString和MultiLineString。
    注意：如果MultiLineString只有一个元素，那么将被视为linestring处理。
    注意：这个函数仅用于处理line表的数据，不能用来处理area表，我们假定所有的linestring都不会围成一个area。
    """
    attrs: Dict[str, Any] = dict(row)
    # 取点列表，添加到OSM Node列表中
    base_geo = loads(attrs["geo"])  # Union[MultiLineString,LineString]
    if isinstance(base_geo, MultiLineString):
        if len(base_geo.geoms) == 1:
            nodes, line = parse_line_string(base_geo.geoms[0], from_gre_table, attrs, "from-multi-line-string-b")
            return nodes, [line], []
        else:
            #as_independent_lines(base_geo, from_gre_table, attrs)  # 将所有Line都视为独立的一个对象存储
            # 将LineStrings视为一组数据，放到一个relation中。
            nodes, lines, relation = parse_lines_as_relation(base_geo, from_gre_table, attrs)
            return nodes, lines, [relation]
    elif isinstance(base_geo, LineString):
        nodes, line = parse_line_string(base_geo, from_gre_table, attrs)
        return nodes, [line], []
    else:
        raise Exception("wrong line type.")

def parse_lines_as_relation(geo_object: MultiLineString, from_gre_table:str, attrs: Dict[str, Any])->Tuple[List[OSMNode], List[OSMLine], OSMRelation]:
    """
    这个方法将MultiLineString视为一组数据，放到一个relation中。MultiLineString是Line的数组，每一个Line还可以有超过2个点。
    这个方法仅适用于"线数据库表（表名以_l结尾）"的处理，我们总是认为这些MultiLineString不会组成一个area。
    1 先分线
    2.1 一条线可能有多个点，把每条线的Points记录下来，存到point_nodes_in_xml。并且记住每一个点的id。
    2.2 把线对象封装成一个<way>，设置id、user、uid、version、changeset、timestamp属性。保存<way>
    3 把所有的<way>放到一个relation下。
    """
    nodes_parsed: List[OSMNode] = []
    lines_parsed: List[OSMLine] = []
    for line in geo_object.geoms:
        #print("line:",line)
        points = list(line.coords)
        osm_line = OSMLine(attrs["rver"], attrs["updated_at"]) # 也可以将创建线对象这一步推迟，先把所有的点全部处理了，那样做的话点对象会具有连续的Id值。
        osm_line.tags["from_table"] = from_gre_table
        osm_line.tags["comment"] = "from-multi-line-string-a"
        # 把引用的点添加到<node>，在<way><nd>中需要记住引用了哪些点。
        for node in points:
            n = OSMNode(node[1], node[0], attrs["rver"], attrs["updated_at"])  # 暂忽略<node><tag>子项
            point_nodes_in_xml.append(n)
            osm_line.nd.append(n.id)
            nodes_parsed.append(n)
        lines_in_xml.append(osm_line)
        lines_parsed.append(osm_line)
    # 把它们包裹在一个relation中，然后设置属性。
    osm_relation = OSMRelation(attrs["rver"], attrs["updated_at"])
    osm_relation.tags["from_table"] = from_gre_table
    osm_relation.tags["comment"] = "multi-line-strings"
    copy_common_attrs(attrs, osm_relation.tags)
    for line in lines_parsed:
        osm_relation.members.append(RelationMember("way", str(line.id), "linestring"))
    relations_in_xml.append(osm_relation)
    return nodes_parsed, lines_parsed, osm_relation

def parse_line_string(geo_line, from_gre_table: str, attrs: Dict[str, Any], comment="from-line-string")->Tuple[List[OSMNode], OSMLine]:
    """
    linestring相对简单，只有一条线。
    """
    # print(from_gre_table, geo_object)
    # 使用shapely库提取点坐标列表
    # print("linestring:", geo_object, ",type:", type(geo_object), ",coords:", list(geo_object.coords))
    nodes:List[OSMNode] = []
    points = list(geo_line.coords)
    # 创建OSM线对象
    osm_line = OSMLine(attrs["rver"], attrs["updated_at"])
    osm_line.tags["from_table"] = from_gre_table
    osm_line.tags["comment"] = comment
    copy_common_attrs(attrs, osm_line.tags)
    for node in points:
        # 组成线的点需要线添加到点列表中，线是对它们的有序引用。
        n = OSMNode(node[1], node[0], attrs["rver"], attrs["updated_at"])  # 暂忽略<node><tag>子项
        point_nodes_in_xml.append(n)
        osm_line.nd.append(n.id) # 线引用了点
        nodes.append(n)
    lines_in_xml.append(osm_line)
    return nodes, osm_line
    # print("linestring way xml:", ET.tostring(osm_line.to_xml_element(), "unicode"))

def common_area(row, from_gre_table)->Tuple[List[OSMNode], List[OSMLine], List[OSMRelation]]:
    attrs: Dict[str, Any] = dict(row)
    #print(row, "\n", attrs, "\n", from_gre_table)
    # 使用shapely库提取点坐标列表
    ar = loads(attrs["geo"])
    #print("area:", ar, ",type:", type(ar))  # shapely.geometry.polygon.Polygon
    #assert isinstance(ar,MultiPolygon) or isinstance(ar, Polygon)
    if isinstance(ar,MultiPolygon):
        return parse_multipolygon(attrs, ar, from_gre_table)
    elif isinstance(ar, Polygon):
        return parse_a_polygon(attrs, ar, from_gre_table)
    else:
        raise Exception("wrong area type!")

def parse_multipolygon(attrs: Dict[str, Any], multipolygon: MultiPolygon, from_gre_table: str)->Tuple[List[OSMNode], List[OSMLine], List[OSMRelation]]:
    """
    对于MultiPolygon有几种情况：
    （1）最简单的情况是multipolygon中只有一个多边形，那就视为单一的polygon来处理，即用<way>标签：
    <way id="6" user="some_user" uid="1" version="1" changeset="1" timestamp="2012-07-10T00:00:00Z">
    <tag k="area" v="yes"/>
    <tag k="name" v="standalone_polygon"/>
    <nd ref="1"/>
    <nd ref="4"/>
    </way>
    （2）如果MultiPolygon是由多个polygon，那么这里不能简单地拆成多个polygon处理，否则多个关联的区域将丢失关系。假设
    海龟岛（turtle rock）是由一个1个主岛和周边的8个岛礁组成的，如果拆成9个polygon，那么主岛和8个岛礁在地图上都会独立展示
    而且它们都具有相同的属性（即9个海龟岛）。所以应当将9个组成部分的地理信息组成Relation然后将属性（attrs）赋予这个Relation。
    注意如果这里某岛礁不是一个简单封闭的polygon而是具有内外边界的polygon，那么会先将它封装成Relation，然后在代表MultiPolygon
    的Relation中引用它（<member type="relation" ref="102" role="sub_polygon"/>）。
    即（2.1）如果所有的polygon都是没有孔洞的简单polygon。那么就简单地在Relation中套多个role="outer"的way
    <Relation>
    <member type="way" ref="101" role="outer"/>
    <member type="way" ref="102" role="outer"/>
    <tag k="type" v="multipolygon"/>
    </Relation>
    （2.2）否则可能是这样的
    <Relation>
    <member type="way" ref="101" role="outer"/>
    <member type="relation" ref="102" role="sub_polygon"/>
    <tag k="type" v="multipolygon"/>
    <tag ... />
    </Relation>
    其中ref="102"如下所示(假设是带有两个孔的polygon)
    <Relation>
    <member type="way" ref="89" role="outer"/>
    <member type="way" ref="90" role="inner"/>
    <member type="way" ref="91" role="inner"/>
    <tag k="type" v="multipolygon"/>
    <tag ... />
    </Relation>
    :param attrs:
    :param multipolygon:
    :param from_gre_table:
    :return:
    """
    count_of_polygon = len(multipolygon.geoms)
    #print("count_of_polygon:", count_of_polygon)
    if not count_of_polygon:  # 错误检测
        raise Exception("bad data, no any polygon!")
    elif count_of_polygon == 1:
        #print("multipolygon对象中只有一个polygon对象，将它视为一个polygon对象处理!")
        return parse_a_polygon(attrs, multipolygon.geoms[0], from_gre_table)
    else:
        ret:Tuple[List[OSMNode], List[OSMLine], List[OSMRelation]] = ([], [], [])
        #print("polygon - multi")
        members : List[Union[OSMRelation, OSMLine]] = []  # members: 每个成员都是一个多边形。
        for i in multipolygon.geoms:
            obj,nodes,lines,relations = multipolygon_components(i, from_gre_table, attrs["rver"], attrs["updated_at"])
            members.append(obj)
            ret[0].extend(nodes)
            ret[1].extend(lines)
            ret[2].extend(relations)

        r = OSMRelation(attrs["rver"], attrs["updated_at"])
        for i in members:
            if isinstance(i, OSMLine):  # 这是一个没有孔洞的多边形，它的外边界总是一条线。见上方函数说明。
                r.members.append(RelationMember("way", str(i.id), "outer"))
            else:  # 这是一个含有孔洞的多边形，用一个Relation封装。
                r.members.append(RelationMember("relation", str(i.id), "sub_polygon"))  # 这个role未来可能修改！
        r.tags["type"] = "multipolygon"
        copy_common_attrs(attrs, r.tags)
        relations_in_xml.append(r)
        ret[2].append(r)
        return ret


def multipolygon_components(polygon: Polygon, from_gre_table: str, rver, updated_at) -> Tuple[Union[OSMLine, OSMRelation], List[OSMNode], List[OSMLine], List[OSMRelation]]:
    """
    将一个multipolygon的polygon封装为OSMLine, OSMRelation。如果是无孔洞的返回OSMLine否则返回OSMRelation。
    不能用于一个单独的polygon，对于polygon应该使用parse_a_polygon方法。
    在这个函数中创建的Node/Way/Relation对象都会添加到对应xml节点列表中。
    """
    points = list(polygon.exterior.coords)  # 先处理外边界
    count_of_points = len(points)
    if count_of_points < 3: # 至少要有三个点才能组成多边形
        raise Exception("至少要有三个点才能组成多边形")
    #print("polygon components外边界:", points)
    if points[0][0] != points[-1][0] or points[0][1] != points[-1][1]:
        raise Exception("polygon的外边界不闭合")

    # 创建OSM线对象
    nodes: List[OSMNode] = []
    line_out = OSMLine(rver, updated_at)
    line_out.tags["from_table"] = from_gre_table
    line_out.tags["area"] = "yes"
    line_out.tags["comment"] = "ExteriorOfArea"
    first_node_id = None
    for i, node in enumerate(points):
        if count_of_points - 1 == i:
            line_out.nd.append(first_node_id)
        else:
            n = OSMNode(node[1], node[0], rver, updated_at)
            if i == 0:
                first_node_id = n.id
            point_nodes_in_xml.append(n)  # 添加点对象<node>
            line_out.nd.append(n.id)      # 给<way>添加点对象的引用
            nodes.append(n)

    # 再检查内边界
    interiors = list(polygon.interiors)
    if len(interiors) == 0: #没有内边界，将它的name设置为standalone_polygon
        line_out.tags["name"] = "standalone_polygon"
        lines_in_xml.append(line_out)
        return line_out, nodes, [line_out], []  # 没有内边界的时候不用创建relation。直接返回OSMLine对象。

    lines:List[OSMLine] = []
    lines_in_xml.append(line_out)
    # 如果有内边界，那么就要返回Relation对象。内边界可能有0个或者多个。
    print("multipolygon_components内边界数量", len(interiors))
    inner_lines: List[OSMLine] = []
    for inner_ring in interiors:
        points = list(inner_ring.coords)
        count_of_points = len(points)
        print("multipolygon_components的内边界", points)
        if points[0][0] != points[-1][0] or points[0][1] != points[-1][1]:
            print("multipolygon_components的内边界不闭合，跳过。")
            continue
        line_obj = OSMLine(rver, updated_at)
        line_obj.tags["from_table"] = from_gre_table
        line_obj.tags["area"] = "yes"
        line_obj.tags["comment"] = "InteriorOfArea"
        first_node_id = None  # 闭环的线段最后一个点是最开始的一个点，即最后一个点不需要转为NodeOSM，引用第一个点的id即可，不然会解析错误。
        for i, node in enumerate(points):
            if count_of_points - 1 == i:
                line_obj.nd.append(first_node_id)  # 不需要为闭环线段的最后一个点创建xml node。在线段中引用第一个点的id。
            else:
                n = OSMNode(node[1], node[0], rver, updated_at)  # 暂忽略<node><tag>子项
                if i == 0:
                    first_node_id = n.id
                # 组成线的点需要线添加到点列表中，线是对它们的有序引用。
                point_nodes_in_xml.append(n)
                line_obj.nd.append(n.id)  # 线引用了点
                nodes.append(n)
        lines_in_xml.append(line_obj)
        inner_lines.append(line_obj)
        lines.append(line_obj)
    # 3. 创建type=multipolygon的Relation并列出所有的outer和inner
    relation = OSMRelation(rver, updated_at)
    relation.tags["type"] = "multipolygon"
    relation.tags["from_table"] = from_gre_table
    relation.tags["comment"] = "PartOfAnotherMultipolygon"
    relation.members.append(RelationMember("way", str(line_out.id),"outer"))
    for i in inner_lines:
        relation.members.append(RelationMember("way", str(i.id), "inner"))
    relations_in_xml.append(relation)
    return relation, nodes, lines, [relation]

def parse_a_polygon(attrs: Dict[str, Any], polygon: Polygon, from_gre_table: str)->Tuple[List[OSMNode], List[OSMLine], List[OSMRelation]]:
    # 1. 处理外边界的坐标点。顺便做一些简单的检查。
    points = list(polygon.exterior.coords)
    count_of_points = len(points)
    if count_of_points < 3: # 至少要有三个点才能组成多边形
        raise Exception("至少要有三个点才能组成多边形")
    #print("polygon外边界:", points)
    if points[0][0] != points[-1][0] or points[0][1] != points[-1][1]:
        raise Exception("polygon的外边界不闭合，跳过")
    # 创建OSM线对象
    nodes: List[OSMNode] = []
    lines: List[OSMLine] = []
    line_out = OSMLine(attrs["rver"], attrs["updated_at"])
    line_out.tags["from_table"] = from_gre_table
    line_out.tags["area"] = "yes"
    line_out.tags["comment"] = "ExteriorOfArea"
    first_node_id = None # 闭环的线段最后一个点是最开始的一个点，即最后一个点不需要转为NodeOSM，引用第一个点的id即可，不然会解析错误。
    for i, node in enumerate(points):
        if count_of_points - 1 == i:
            line_out.nd.append(first_node_id)  # 不需要为闭环线段的最后一个点创建xml node。在线段中引用第一个点的id。
        else:
            n = OSMNode(node[1], node[0], attrs["rver"], attrs["updated_at"])  # 暂忽略<node><tag>子项
            if i == 0:
                first_node_id = n.id
            # 组成线的点需要线添加到点列表中，线是对它们的有序引用。
            point_nodes_in_xml.append(n)
            nodes.append(n)
            line_out.nd.append(n.id)  # 线引用了点
    # print("Polygon - area外边界way xml:", ET.tostring(line_out.to_xml_element(), "unicode"))

    interiors = list(polygon.interiors)
    if len(interiors) == 0: #没有内边界，将它的name设置为standalone_polygon
        line_out.tags["name"] = "standalone_polygon"
        copy_common_attrs(attrs, line_out.tags)
        lines_in_xml.append(line_out)
        return nodes, [line_out], []  # 没有内边界的时候不用创建relation。直接返回。

    lines.append(line_out)
    lines_in_xml.append(line_out)
    # 2. 处理内边界，Polygon可能存在0个或者多个孔洞，即0个或多个内边界。
    #print("内边界数量:", len(interiors))
    inner_lines: List[OSMLine] = []
    for inner_ring in interiors:
        points = list(inner_ring.coords)
        count_of_points = len(points)
        #print("polygon内边界:", points)
        if points[0][0] != points[-1][0] or points[0][1] != points[-1][1]:
            print("polygon的内边界不闭合，跳过")
            continue
        # 创建OSM线对象
        line = OSMLine(attrs["rver"], attrs["updated_at"])
        line.tags["from_table"] = from_gre_table
        line.tags["area"] = "yes"
        line.tags["comment"] = "InteriorOfArea"
        first_node_id = None  # 闭环的线段最后一个点是最开始的一个点，即最后一个点不需要转为NodeOSM，引用第一个点的id即可，不然会解析错误。
        for i, node in enumerate(points):
            if count_of_points - 1 == i:
                line.nd.append(first_node_id)  # 不需要为闭环线段的最后一个点创建xml node。在线段中引用第一个点的id。
            else:
                n = OSMNode(node[1], node[0], attrs["rver"], attrs["updated_at"])  # 暂忽略<node><tag>子项
                if i == 0:
                    first_node_id = n.id
                # 组成线的点需要线添加到点列表中，线是对它们的有序引用。
                point_nodes_in_xml.append(n)
                line.nd.append(n.id)  # 线引用了点
                nodes.append(n)
        lines_in_xml.append(line)
        inner_lines.append(line)
        lines.append(line)

    # 3. 创建Relation，把内边界塞到外边界中
    relation = OSMRelation(attrs["rver"], attrs["updated_at"])
    relation.tags["type"] = "multipolygon"
    relation.tags["from_table"] = from_gre_table
    copy_common_attrs(attrs, relation.tags)
    relation.members.append(RelationMember("way", str(line_out.id),"outer"))
    for i in inner_lines:
        relation.members.append(RelationMember("way", str(i.id), "inner"))
    relations_in_xml.append(relation)
    return nodes, lines, [relation]

